#region license
// Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Taken and modified from the original

# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Strongly-typed resource facade wrapping a <ResourceManager>."""

namespace Grawlix.Meta
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Resources
import System
import System.Collections as SC
import System.IO
import System.Resources

transient class ResourceFacadeAttribute(AbstractAstAttribute):
"""
Creates a resource facade wrapping a <ResourceManager>.
Authors: Sorin Ionescu (sorin.ionescu@gmail.com)
"""
	[property(Base)]
	private _base as Ast.ReferenceExpression
	"""The base identifier of the resource file."""
	
	[property(Assembly)]
	private _assembly as Ast.ReferenceExpression
	"""The full name of the assembly to use."""
	
	private _resourceFacade as Ast.ClassDefinition
	"""The node the attribute is applied to."""
	
	private _reader as ResourceReader
	"""A set of resources to wrap."""
	
	private _assemblyExpression as Ast.Expression
	"""The assembly that holds the resources."""
	
	override def Apply([required] targetNode as Ast.Node):
	"""
	Applies the attribute to the node.
	Param targetNode: The node to apply the attribute to.
	Raises NotSupportedException: [node] is not a <ClassDefinition>.
	"""
		_resourceFacade = targetNode as Ast.ClassDefinition
		raise NotSupportedException('Node must be a ClassDefinition.') \
			if _resourceFacade is null
		
		_resourceFacade.Modifiers |= \
			Ast.TypeMemberModifiers.Transient | \
			Ast.TypeMemberModifiers.Final
		
		if _base is not null:
			baseName = _base.Name
		else:
			baseName = _resourceFacade.FullName
		
		compilingAssemblyName = Path.GetFileNameWithoutExtension(
			Parameters.OutputAssembly)
		
		if _assembly is not null:
			assemblyName = _assembly.Name
		else:
			assemblyName = compilingAssemblyName
		
		entryNamePattern = /^[a-zA-Z_]\w*$/
		resourceNamePattern = regex(
			"""^""" + baseName + \
			"""(\.(\w{2,3}|\w{2,3}-\w{2,3}|\w{2,3}-\w{2,3}-\w{0,4}))?""" + 
			"""\.resources$""")
		
		if assemblyName == compilingAssemblyName:
			FindLocalReader(resourceNamePattern)
		else:
			FindExternalReader(assemblyName, resourceNamePattern)
		
		if _reader is null:
			raise FileNotFoundException("${baseName}[.culture].resources")
		using _reader:
			_assemblyExpression.LexicalInfo = LexicalInfo
			
			CreateManagerProperties(baseName, _assemblyExpression)
			
			for entry as SC.DictionaryEntry in _reader:
				CreateResourceProperty(
					entry.Key.ToString(),
					entry.Value.GetType(),
					entryNamePattern)
	
	private def AddMember(member as Ast.TypeMember):
	"""
	Adds a member to this type.
	Param member: The member to add.
	"""
		member.LexicalInfo = LexicalInfo
		_resourceFacade.Members.Add(member)
	
	private def FindLocalReader(resourceNamePattern as regex):
	"""
	Grabs the reader and assembly reference from the local assembly.
	Param baseName: The base identifier of the resource file.
	Param resourceNamePattern: A pattern to identify valid file names.
	"""
		for resourceFile as FileResource in Parameters.Resources:
			if resourceFile.Name =~ resourceNamePattern:
				_reader = ResourceReader(resourceFile.FileName)
				_assemblyExpression = \
					[| System.Reflection.Assembly.GetExecutingAssembly() |]
	
	private def FindExternalReader(
		assemblyName as string,
		resourceNamePattern as regex):
	"""
	Grabs the reader and assembly reference from an external assembly.
	Param baseName: The base identifier of the resource file.
	Param assemblyName: The name of the assembly to reference.
	Param resourceNamePattern: A pattern to identify valid file names.
	"""
		for assembly in Parameters.References:
			continue unless assembly.GetName().Name == assemblyName
			for resourceName in assembly.GetManifestResourceNames():
				continue unless resourceName =~ resourceNamePattern
				_reader = ResourceReader(
					assembly.GetManifestResourceStream(resourceName))
				
				types = assembly.GetTypes()
				if len(types) > 0:
				# If the assembly has types, use a type instead
				# of loading the assembly twice.
					type = CodeBuilder.CreateTypeReference(types[0])
					_assemblyExpression = [|
						System.Reflection.Assembly.GetAssembly(
							typeof($type))
					|]
				else:
					_assemblyExpression = [|
						System.Reflection.Assembly. \
							GetAssembly($assemblyName)
					|]
				return
	
	private def CreateManagerProperties(
		baseName as string,
		assembly as Ast.Expression):
	"""
	Creates the two required properties.
	Param baseName: The base resource name.
	Param assembly: An expression which evaluates to
		the assembly to reference.
	"""
		resourceManager = [|
			[property(ResourceManager)]
			private static _resourceManager = \
				System.Resources.ResourceManager($baseName, $assembly)
		|]
		AddMember(resourceManager)
		resourceCulture = [|
			[property(ResourceCulture)]
			private static _resourceCulture as System.Globalization.CultureInfo
		|]
		AddMember(resourceCulture)
	
	private def CreateResourceProperty(
		name as string,
		type as Type,
		entryNamePattern as regex):
	"""
	Creates a wrapped resource property.
	Param name: The name of the resource.
	Param type: The type of the resource.
	Param entryNamePattern: A pattern to identify valid identifier names.
	"""
		if not name =~ entryNamePattern:
			warning = CompilerWarningFactory.CustomWarning(
				LexicalInfo,
				"Resource entry ${name} is not " + \
				"a valid identifier and " + \
				"cannot be added to the resource facade.")
			Context.Warnings.Add(warning)
			return
		
		nameReference = Ast.ReferenceExpression(name)
		typeReference = CodeBuilder.CreateTypeReference(type)
		resourceProperty = [|
			public static $nameReference as $typeReference:
				get:
					return ResourceManager.GetObject($name, ResourceCulture)
		|]
		AddMember(resourceProperty)
	
	